package cn.jbolt.ai.core.chain.node.action.klb;

import cn.hutool.core.util.StrUtil;
import cn.jbolt.ai.core.chain.node.base.AppConfigConstant;
import cn.jbolt.ai.core.chain.node.base.LlmConfig;
import cn.jbolt.ai.app.service.AiAppMessageService;
import cn.jbolt.ai.app.service.AiAppService;
import cn.jbolt.ai.core.chain.ChainContext;
import cn.jbolt.ai.core.chain.node.base.BaseNode;
import cn.jbolt.ai.core.chain.node.base.RenderType;
import cn.jbolt.ai.core.chain.node.provider.EventNodeProvider;
import cn.jbolt.ai.klb.FileType;
import cn.jbolt.ai.klb.entity.AiDoc;
import cn.jbolt.util.JSONUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.jboltai.capability.model.JBoltAI;
import com.jboltai.event.Event;
import com.jboltai.event.ThinkStatus;
import com.jboltai.event.ai.aiknowledge.AIKnowledgeEvent;
import com.jboltai.event.signal.FailEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;


/**
 * 知识库回答的节点
 */
@Component
public class KlbChatNodeProvider extends BaseNode implements EventNodeProvider<Event> {

    @Autowired
    private AiAppService aiAppService;
    @Autowired
    private AiAppMessageService aiAppMessageService;
    private static final Logger logger = LoggerFactory.getLogger(KlbChatNodeProvider.class);

    public static final String CHAT_WITH_KNOWLEDGE_ONLY = "你是一个专业的问答助手。请严格按照以下规则回答用户提问。\n" +
            "\n" +
            "## 背景知识\n" +
            "{background}\n" +
            "\n" +
            "## 用户问题\n" +
            "{question}\n" +
            "\n" +
            "## 回答规则\n" +
            "\n" +
            "### 强制性规则\n" +
            "1. **背景知识判断**（最高优先级）:\n" +
            "   - 当背景知识为空：必须直接返回且仅返回：\"<nomatch/>\"\n" +
            "   - 当背景知识与用户问题完全无关：必须直接返回且仅返回：\"<nomatch/>\"\n" +
            "   - 上述两种情况下，禁止使用你的通用知识回答，禁止生成任何其他内容\n" +
            "\n" +
            "2. **内容来源限制**:\n" +
            "   - 严格仅使用背景知识中的内容回答\n" +
            "   - 禁止输出背景知识中不存在的虚构信息\n" +
            "\n" +
            "### 媒体与链接处理\n" +
            "   - 完整保留所有原始URL和相对路径（图片、视频、音频、附件等）\n" +
            "   - 使用markdown格式正确渲染所有链接和媒体\n" +
            "   - 链接格式必须与背景知识中完全一致，不修改任何URL\n" +
            "\n" +
            "### 格式与输出要求\n" +
            "- 使用清晰的markdown格式组织回答\n" +
            "- 重要信息使用**加粗**标记\n" +
            "- 禁止使用\"根据背景知识\"等引导语开头\n" +
            "- 不在整个回答外添加```markdown```标记\n" +
            "- 不展示思考过程，直接提供最终答案\n" +
            "\n" +
            "### 特殊情况例外\n" +
            "- 仅对纯问候语、感谢或社交用语：可使用简短自然语言直接回复\n" +
            "- 此例外不适用于任何实质性问题，即使是简单问题\n" +
            "\n" +
            "请严格执行这些规则，特别是关于空背景知识的处理，不允许有任何例外情况。";


    public static final String CHAT_WITH_KNOWLEDGE_ONLY_HTML = "你是一个专业的问答助手。请严格按照以下规则回答用户提问。\n" +
            "\n" +
            "## 背景知识\n" +
            "{background}\n" +
            "\n" +
            "## 用户问题\n" +
            "{question}\n" +
            "\n" +
            "## 回答规则\n" +
            "\n" +
            "### 强制性规则\n" +
            "1. **背景知识判断**（最高优先级）:\n" +
            "   - 当背景知识为空：必须直接返回且仅返回：\"<nomatch/>\"\n" +
            "   - 当背景知识与用户问题完全无关：必须直接返回且仅返回：\"<nomatch/>\"\n" +
            "   - 上述两种情况下，禁止使用你的通用知识回答，禁止生成任何其他内容\n" +
            "\n" +
            "2. **内容来源限制**:\n" +
            "   - 严格仅使用背景知识中的内容回答\n" +
            "   - 禁止输出背景知识中不存在的虚构信息\n" +
            "\n" +
            "### 媒体与链接处理\n" +
            "   - 完整保留所有原始URL和相对路径（图片、视频、音频、附件等）\n" +
            "   - 链接格式必须与背景知识中完全一致，不修改任何URL\n" +
            "\n" +
            "### 特殊情况例外\n" +
            "- 仅对纯问候语、感谢或社交用语：可使用简短自然语言直接回复\n" +
            "- 此例外不适用于任何实质性问题，即使是简单问题\n" +
            "\n" +
            "### 回答呈现规则\n" +
            "当回答不是\"<nomatch/>\"时，必须按照以下方式呈现回答内容：\n" +
            "\n" +
            "1. **基本布局**\n" +
            "   - 创建一个美观、有设计感的HTML布局\n" +
            "   - 设计风格要简洁、现代、专业且具有高级感\n" +
            "   - 使用Tailwind CSS进行样式设计\n" +
            "   - 根据内容类型选择适当的布局（卡片式、列表式、问答式等）\n" +
            "   - 添加合适的间距、边距和圆角效果，提升视觉舒适度" +
            "\n" +
            "2. **排版与样式**\n" +
            "   - 使用清晰的字体层级（标题、副标题、正文等）\n" +
            "   - 重要内容使用色彩、粗体或其他视觉元素强调\n" +
            "   - 正文内容保持适当的行高和字间距，确保良好的可读性\n" +
            "   - 使用与内容相符的柔和配色方案\n" +
            "\n" +
            "3. **图标与视觉元素**\n" +
            "   - 适当使用Font Awesome图标增强视觉表达\n" +
            "   - 根据内容主题选择相关图标（如问题用fa-question-circle，提示用fa-info-circle等）\n" +
            "   - 为不同区块添加适当的视觉分隔符\n" +
            "\n" +
            "4. **媒体内容处理**\n" +
            "   - 图片：添加合适的边框、阴影和圆角，并确保响应式布局\n" +
            "   - 视频：使用优雅的播放器样式，添加播放按钮和控制栏\n" +
            "   - 音频：设计简洁的音频播放控制条\n" +
            "   - 附件：使用带图标的下载按钮样式\n" +
            "\n" +
            "5. **交互元素（如需使用原生JS）**\n" +
            "   - 适当添加折叠/展开功能，处理长文本内容\n" +
            "   - 为复杂表格添加排序、筛选功能\n" +
            "   - 添加回到顶部按钮（对于长回答）\n" +
            "   - 代码高亮和复制功能（如包含代码块）\n" +
            "\n" +
            "6. **响应式设计**\n" +
            "   - 确保布局在不同尺寸下均能良好显示\n" +
            "   - 使用Tailwind的响应式类控制布局\n" +
            "\n" +
            "7. **特殊内容样式**\n" +
            "   - 列表：使用有设计感的列表样式，添加适当的项目符号或数字样式\n" +
            "   - 表格：清晰的表头、条纹样式和合适的边框\n" +
            "   - 引用：使用优雅的引用样式，添加引用符号或左侧边框\n" +
            "   - 提示框：根据内容类型使用不同色彩的提示框（信息、警告、成功等）\n" +
            "\n" +
            "8. **代码输出要求**\n" +
            "   - 只输出body内的HTML代码，不包含head、html标签\n" +
            "   - 内联样式使用Tailwind类，不使用自定义CSS\n" +
            "   - JavaScript代码必须是原生JS，不引入第三方库\n" +
            "   - 确保代码完整、规范且可直接在iframe中运行\n" +
            "   - 代码外层禁止用```html ```包裹\n" +
            "\n" +
            "\n" +
            "请严格执行这些规则，特别是关于空背景知识的处理，不允许有任何例外情况。同时确保HTML输出符合规范，能直接在iframe中正确显示。";

    /**
     * 根据知识库回答的模版
     */
    public static final String CHAT_WITH_KNOWLEDGE = "你是一个专业的知识问答助手。请按照以下规则回答用户问题：\n" +
            "\n" +
            "## 回答优先级\n" +
            "1. 首要回答来源：提供的背景知识\n" +
            "2. 次要回答来源（仅当背景知识不相关或为空时）：你的通用知识\n" +
            "\n" +
            "## 背景知识\n" +
            "{background}\n" +
            "\n" +
            "## 用户问题\n" +
            "{question}\n" +
            "\n" +
            "## 回答规则\n" +
            "\n" +
            "### 基于背景知识时（当背景知识非空且相关）\n" +
            "1. **内容准确性**\n" +
            "   - 严格基于背景知识中已有信息回答\n" +
            "   - 不添加、不臆测背景知识中不存在的内容\n" +
            "   - 背景知识无法完全回答问题时，明确指出信息限制\n" +
            "\n" +
            "2. **媒体与链接处理**\n" +
            "   - 完整保留所有原始URL和相对路径（图片、视频、音频、附件等）\n" +
            "   - 使用markdown格式正确渲染所有链接和媒体\n" +
            "   - 链接格式必须与背景知识中完全一致，不修改任何URL\n" +
            "   \n" +
            "3. **回答格式**\n" +
            "   - 使用清晰的markdown格式\n" +
            "   - 重要内容使用**加粗**标记\n" +
            "   - 合理分段，提高可读性\n" +
            "   - 直接输出最终答案，不添加引导语或前缀\n" +
            "\n" +
            "### 使用通用知识时（当背景知识为空或不相关）\n" +
            "1. 完全以你的通用知识回答用户问题\n" +
            "2. 严格遵循用户指定的格式和要求\n" +
            "\n" +
            "### 通用规则\n" +
            "1. **禁止事项**\n" +
            "   - 不使用\"根据背景知识\"等引导语\n" +
            "   - 不输出思考过程\n" +
            "   - 不在回答外层添加```markdown```包裹\n" +
            "   \n" +
            "2. **特殊问题处理**\n" +
            "   - 问候语、客套话：直接以自然对话方式简短回复\n" +
            "   - 明确指出背景知识不足的情况，但不强调使用了通用知识\n" +
            "\n" +
            "请直接输出最终答案，无需解释如何应用这些规则。";


    /**
     * 使用html渲染内容
     */
    public static final String CHAT_WITH_KNOWLEDGE_HTML = "你是一个专业的知识问答助手。请按照以下规则回答用户问题，并使用精美的HTML界面展示：\n" +
            "\n" +
            "## 回答优先级\n" +
            "1. 首要回答来源：提供的背景知识\n" +
            "2. 次要回答来源（仅当背景知识不相关或为空时）：你的通用知识\n" +
            "\n" +
            "## 背景知识\n" +
            "{background}\n" +
            "\n" +
            "## 用户问题\n" +
            "{question}\n" +
            "\n" +
            "## 回答规则\n" +
            "### 基于背景知识时（当背景知识非空且相关）\n" +
            "1. **内容准确性**\n" +
            "   - 严格基于背景知识中已有信息回答\n" +
            "   - 不添加、不臆测背景知识中不存在的内容\n" +
            "   - 背景知识无法完全回答问题时，明确指出信息限制\n" +
            "2. **媒体与链接处理**\n" +
            "   - 完整保留所有原始URL和相对路径（图片、视频、音频、附件等）\n" +
            "   - 确保所有链接和媒体在HTML中正确渲染\n" +
            "   - 链接格式必须与背景知识中完全一致，不修改任何URL\n" +
            "   \n" +
            "3. **回答格式**\n" +
            "   - 使用清晰的HTML格式并应用Tailwind CSS样式\n" +
            "   - 重要内容使用样式强调\n" +
            "   - 合理分段，提高可读性\n" +
            "   - 直接输出最终答案，不添加引导语或前缀\n" +
            "\n" +
            "### 使用通用知识时（当背景知识为空或不相关）\n" +
            "1. 完全以你的通用知识回答用户问题\n" +
            "2. 严格遵循用户指定的格式和要求\n" +
            "\n" +
            "### 通用规则\n" +
            "1. **禁止事项**\n" +
            "   - 不使用\"根据背景知识\"等引导语\n" +
            "   - 不输出思考过程\n" +
            "   - 不在回答外层添加markdown包裹\n" +
            "   \n" +
            "2. **特殊问题处理**\n" +
            "   - 问候语、客套话：直接以自然对话方式简短回复\n" +
            "   - 明确指出背景知识不足的情况，但不强调使用了通用知识\n" +
            "\n" +
            "## HTML UI设计规则\n" +
            "1. **基本布局**\n" +
            "   - 创建一个美观、有设计感的HTML布局\n" +
            "   - 设计风格要简洁、现代、专业且具有高级感\n" +
            "   - 使用Tailwind CSS进行样式设计\n" +
            "   - 根据内容类型选择适当的布局（卡片式、列表式、问答式等）\n" +
            "   - 添加合适的间距、边距和圆角效果，提升视觉舒适度" +
            "\n" +
            "2. **排版与样式**\n" +
            "   - 使用清晰的字体层级（标题、副标题、正文等）\n" +
            "   - 重要内容使用色彩、粗体或其他视觉元素强调\n" +
            "   - 正文内容保持适当的行高和字间距，确保良好的可读性\n" +
            "   - 使用与内容相符的柔和配色方案\n" +
            "\n" +
            "3. **图标与视觉元素**\n" +
            "   - 适当使用Font Awesome图标增强视觉表达\n" +
            "   - 根据内容主题选择相关图标（如问题用fa-question-circle，提示用fa-info-circle等）\n" +
            "   - 为不同区块添加适当的视觉分隔符\n" +
            "\n" +
            "4. **媒体内容处理**\n" +
            "   - 图片：添加合适的边框、阴影和圆角，并确保响应式布局\n" +
            "   - 视频：使用优雅的播放器样式，添加播放按钮和控制栏\n" +
            "   - 音频：设计简洁的音频播放控制条\n" +
            "   - 附件：使用带图标的下载按钮样式\n" +
            "\n" +
            "5. **交互元素（如需使用原生JS）**\n" +
            "   - 适当添加折叠/展开功能，处理长文本内容\n" +
            "   - 为复杂表格添加排序、筛选功能\n" +
            "   - 添加回到顶部按钮（对于长回答）\n" +
            "   - 代码高亮和复制功能（如包含代码块）\n" +
            "\n" +
            "6. **响应式设计**\n" +
            "   - 确保布局在不同尺寸下均能良好显示\n" +
            "   - 使用Tailwind的响应式类控制布局\n" +
            "\n" +
            "7. **特殊内容样式**\n" +
            "   - 列表：使用有设计感的列表样式，添加适当的项目符号或数字样式\n" +
            "   - 表格：清晰的表头、条纹样式和合适的边框\n" +
            "   - 引用：使用优雅的引用样式，添加引用符号或左侧边框\n" +
            "   - 提示框：根据内容类型使用不同色彩的提示框（信息、警告、成功等）\n" +
            "\n" +
            "8. **代码输出要求**\n" +
            "   - 只输出body内的HTML代码，不包含head、html标签\n" +
            "   - 内联样式使用Tailwind类，不使用自定义CSS\n" +
            "   - JavaScript代码必须是原生JS，不引入第三方库\n" +
            "   - 确保代码完整、规范且可直接在iframe中运行\n" +
            "   - 代码外层禁止用```html ```包裹\n" +
            "\n" +
            "请直接输出格式精美的HTML代码，无需解释如何应用这些规则。";


    public static void main(String[] args) {
        System.out.println(CHAT_WITH_KNOWLEDGE);
    }


    /**
     * 是否为未匹配到的标记
     */
    private static final String NOMATCH_KEY = "_match_success";

    public Event provide(JsonNode nodeConfig, ChainContext context) {
        try {
            LlmConfig llmConfig = LlmConfig.from(nodeConfig.get(AppConfigConstant.CONFIG_TYPE_LLM));
            KlbChatNodeConfig klbChatConfig = KlbChatNodeConfig.from(nodeConfig.get(AppConfigConstant.CONFIG_TYPE_KLB_CHAT));
            if (klbChatConfig == null) {
                logger.error("APP [{}:{}] 节点未配置", context.getAiApp().getSn(), context.getAiApp().getName());
                throw new RuntimeException("节点未配置");
            }
            List<AiDoc> docs = new ArrayList<>();
            try {

                List<AiDoc> res = context.getAttr(klbChatConfig.getKlbResultField());
                if (res != null) {
                    docs.addAll(res);
                }
            } catch (Exception e) {
                logger.error("ChatNodeHandler process 异常,异常原因", e);
                context.sendErrorMsg("知识库查询结果配置不正确");
                return new FailEvent("知识库查询结果配置不正确");
            }



            //生成背景知识
            List<String> background = generateBackgroundText(docs);
            AIKnowledgeEvent event = JBoltAI.chatWithKnowledge(null, background, klbChatConfig.getNoMatchReply());
            event.setKnowledgeOnly(klbChatConfig.isOnlyKlb());
            if (Objects.equals(klbChatConfig.getShowWithHtml(), true)) {
                //如果开启了加强显示
                llmConfig.setRenderType(RenderType.html);
                event.setPromptTpl(klbChatConfig.isOnlyKlb() ? CHAT_WITH_KNOWLEDGE_ONLY_HTML : CHAT_WITH_KNOWLEDGE_HTML);
            } else {

                event.setPromptTpl(klbChatConfig.isOnlyKlb() ? CHAT_WITH_KNOWLEDGE_ONLY : CHAT_WITH_KNOWLEDGE);
            }
            //根据配置初始化event
            super.configLlm(llmConfig, context, event);
            StringBuilder tempCache = new StringBuilder(); //缓存输出的内容，用于判断是否未匹配到，流式输出时，<nomatch/>会被拆分输出
            event.onSuccess((e, res) -> {
                if (Objects.equals(llmConfig.isOutputToResponse(), true)) {
                    if (Objects.equals(context.getAttr(NOMATCH_KEY), false)) {
                        //如果已经识别为未匹配到，直接忽略后面的输出
                        return;
                    }
                    tempCache.append(res.getContent());
                    if (tempCache.length() < 20) {
                        return;
                    }
                    //如果配置项要求 内容返回到响应里，那就输出，默认是true的
                    //构建相应信息
                    outputToResponse(context, e,  tempCache, klbChatConfig, llmConfig);
                }
            });
            event.setThinkingEnable(Objects.equals(llmConfig.getOutputThinking(), true));
            //输出思考过程
            event.onThinking((e, thinking, status) -> {
                if (status == ThinkStatus.START) {
                    context.sendThinking(ThinkStatus.START, thinking, null);
                } else if (status == ThinkStatus.RUNNING) {
                    context.sendThinking(ThinkStatus.RUNNING, thinking, null);
                } else if (status == ThinkStatus.COMPLETE) {
                    context.sendThinking(ThinkStatus.COMPLETE, thinking, e.getThinkDuration());
                }
                context.getResponseMsg().appendThinking(status, thinking);
            });
            event.onFail((e, error) -> {
                logger.error("ChatWithKlbn节点执行发生异常,异常原因:{}", error.getFullMsg());
            });

            event.onComplete((e) -> {

                switch (e.getState()) {
                    case SUCCESS:
                        if (Objects.equals(context.getAttr(NOMATCH_KEY), false) == false && tempCache.length() > 0) {
                            //还有剩的没输出完的
                            outputToResponse(context, e, tempCache, klbChatConfig, llmConfig);
                        }
                        //事件执行成功
                        if (StrUtil.isNotBlank(klbChatConfig.getOutputField())) {
                            //配置项要求，把结果输出到上下文
                            context.setAttr(klbChatConfig.getOutputField(), Objects.equals(context.getAttr(NOMATCH_KEY), false) ? klbChatConfig.getNoMatchReply() : e.getResult());
                        }

                        //向前端推送参考
                        if (e.isMatchSuccess() && klbChatConfig.isShowDocRefers()) {
                            context.sendReference(docs);
                        }
                    case CANCEL:
                        if (Objects.equals(llmConfig.isPersistDb(), true)) {//成功或者取消 需要持久化数据
                            if (Objects.equals(context.getAttr(NOMATCH_KEY), false) ) {
                                context.getResponseMsg().appendContent(klbChatConfig.getNoMatchReply(), null);
                            } else {
                                //把答案给存储
                                context.getResponseMsg().appendContent(e.getAiChatEvent().getResult() != null ?e.getAiChatEvent().getResult().getContent() : null, llmConfig.getRenderType());
                                //匹配到了
                                context.getResponseMsg().setDocs(JSONUtil.toJson(docs.stream().map(AiDoc::getId).collect(Collectors.toList())));
                            }
                        }
                        break;
                    case FAIL:

                }
                event.setMatchSuccess(Objects.equals(context.getAttr(NOMATCH_KEY), false) == false);

            });
            return event;
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("ChatNodeHandler process 异常,异常原因", e);
            context.sendErrorMsg("网络异常,请稍后重试....");
        }
        return null;
    }

    private static void outputToResponse(ChainContext context, AIKnowledgeEvent e, StringBuilder tempCache, KlbChatNodeConfig klbChatConfig, LlmConfig llmConfig) {
        if (e.getAttr(NOMATCH_KEY) == null && (tempCache.indexOf("<nomatch/>") > -1 || tempCache.indexOf("<nomatch") > -1)) {
            context.sendResponseMsg(klbChatConfig.getNoMatchReply(), null);
            context.setAttr(NOMATCH_KEY, false);
            tempCache.delete(0, tempCache.length());
        } else {
            context.sendResponseMsg(tempCache.toString(), llmConfig.getRenderType());
            context.setAttr(NOMATCH_KEY, true);
            tempCache.delete(0, tempCache.length());
        }
    }


    private List<String> generateBackgroundText(List<AiDoc> docs) {
        if (docs == null || docs.size() == 0) {
            return Collections.emptyList();
        }
        //将结果保存到 输出字段中
        List<String> list = new ArrayList<>();
        for (AiDoc aiDoc : docs) {
            if (FileType.QA.getCode().equals(aiDoc.getDocType())) {
                list.add(aiDoc.getContent() + "\n" + aiDoc.getAnswer());
            } else {
                list.add(aiDoc.getContent());
            }

        }
        return list;
    }

}
